using System;
using System.Xml;
using System.Reflection;
using System.Collections.Generic;

namespace Elephant.Framework.XML
{
    internal sealed class EntityTemplateReader
    {
        public static EntityTemplate ReadFrom(string filename)
        {
            EntityTemplate template = new EntityTemplate();
            
            using (XmlTextReader reader = new XmlTextReader(filename)) {
                while (reader.Read()) {
                    if (reader.NodeType == XmlNodeType.Element) {
                        if (reader.Name.Equals("Entity", StringComparison.InvariantCultureIgnoreCase)) {
                            string tmp = "";
                            ReadEntity(reader, out tmp);//out template.Name);

                            template.Name = tmp;
                        } else if (reader.Name.Equals("Components", StringComparison.InvariantCultureIgnoreCase)) {
                            ReadComponents(reader.ReadSubtree(), template.ComponentTemplates);
                        }
                    }
                }

                reader.Close();
            }

            return template;
        }

        /// <summary>
        /// Reads entity information from the current node in reader.
        /// </summary>
        /// <param name="reader"></param>
        /// <param name="name"></param>
        /// <returns></returns>
        private static bool ReadEntity(XmlReader reader, out string name)
        {
            name = null;

            if (!reader.Name.Equals("Entity", StringComparison.InvariantCultureIgnoreCase)) {
                throw new Exception("Current node is not an Entity element.");
            }

            if (!reader.HasAttributes) {
                throw new Exception("Entity is missing one or more important attributes.");
            }

            bool result = false;

            // this way we can go through multiple attributes, where some may be optional
            while (reader.MoveToNextAttribute()) {
                // reached Name attribute
                if (reader.Name.Equals("Name", StringComparison.InvariantCultureIgnoreCase)) {
                    // grab the value of Name attribute
                    name = reader.GetAttribute(reader.Name);

                    if ((result = (name != null && name.Length > 0)) == false) {
                        throw new Exception("Name attribute cannot be empty.");
                    }
                }
            }

            return result;
        }

        /// <summary>
        /// Reads any components found in the readers tree into specified list.
        /// </summary>
        /// <param name="reader"></param>
        /// <param name="componentTemplates"></param>
        /// <returns></returns>
        private static bool ReadComponents(XmlReader reader, List<ComponentTemplate> componentTemplates)
        {
            // gotta read once to reach starting element
            reader.Read();

            if (!reader.Name.Equals("Components", StringComparison.InvariantCultureIgnoreCase)) {
                throw new Exception("Current node is not a Components element.");
            }

            bool result = false;

            while (reader.Read()) {
                if (reader.NodeType == XmlNodeType.Element) {
                    if (reader.Name.Equals("Component", StringComparison.InvariantCultureIgnoreCase)) {
                        ComponentTemplate componentTemplate = null;

                        // reached a component definition, read it specifically and pump out its template
                        if (ReadComponent(reader.ReadSubtree(), out componentTemplate)) {
                            componentTemplates.Add(componentTemplate);
                        }
                    }
                }
            }

            return result;
        }

        /// <summary>
        /// Reads information about a component out into specified component template object.
        /// </summary>
        /// <param name="reader"></param>
        /// <param name="componentTemplate"></param>
        /// <returns></returns>
        private static bool ReadComponent(XmlReader reader, out ComponentTemplate componentTemplate)
        {
            // gots ta read once to get to starting element
            reader.Read();

            if (!reader.Name.Equals("Component", StringComparison.InvariantCultureIgnoreCase)) {
                throw new Exception("Current node is not a Component element.");
            }

            if (!reader.HasAttributes) {
                throw new Exception("Component is missing one or more important attributes.");
            }

            bool result = false;

            // establish variables that are needed to build a component template
            string typeName = "";
            string assemblyName = "";
            Dictionary<string, string> parameters = new Dictionary<string, string>();

            // this way we can go through multiple attributes, where some may be optional
            while (reader.MoveToNextAttribute()) {
                if (reader.Name.Equals("Type", StringComparison.InvariantCultureIgnoreCase)) {
                    // save the type so we can instantiate once we know if there's any parameters
                    typeName = reader.GetAttribute(reader.Name);

                    if ((result = (typeName != null && typeName.Length > 0)) == false) {
                        throw new Exception("Type attribute cannot be empty.");
                    }
                } else if (reader.Name.Equals("Assembly", StringComparison.InvariantCultureIgnoreCase)) {
                    assemblyName = reader.GetAttribute(reader.Name);

                    if ((result = (assemblyName != null && assemblyName.Length > 0)) == false) {
                        throw new Exception("Assembly attribute cannot be empty.");
                    }
                } else {
                    // anything else is considered parameters, and can have whatever name you wish
                    parameters.Add(reader.Name, reader.GetAttribute(reader.Name));
                }
            }

            // move reader back to element node
            reader.MoveToElement();

            Dictionary<string, string> defaults = new Dictionary<string, string>();

            // read value setting information, no need to read subtree as we want to 
            // use the current tree provided for this method
            ReadComponentDefaults(reader, defaults);

            componentTemplate = new ComponentTemplate();
            componentTemplate.Type = String.Format("{0}, {1}", typeName, assemblyName);
            componentTemplate.Parameters = parameters;
            componentTemplate.Defaults = defaults;

            return result;
        }

        /// <summary>
        /// Reads default value information about a component.
        /// </summary>
        /// <param name="reader"></param>
        /// <param name="values"></param>
        /// <returns></returns>
        private static bool ReadComponentDefaults(XmlReader reader, Dictionary<string, string> defaults)
        {
            bool result = false;

            while (reader.Read()) {
                // for some reason Read()'ing only continues on the elements at the same depth, so by letting this loop
                // continue, we can read all default values from more properties/fields 
                if (reader.NodeType == XmlNodeType.Element) {
                    if (!reader.Name.Equals("Component", StringComparison.InvariantCultureIgnoreCase)) {
                        // the first element we find, that is not named Component is the start of the path
                        result = ReadcomponentDefaultValues(reader.ReadSubtree(), defaults);
                    }
                }
            }

            return result;
        }

        private static bool ReadcomponentDefaultValues(XmlReader reader, Dictionary<string, string> defaults)
        {
            bool result = false;

            // oO
            reader.Read();

            string currentPath = reader.Name;

            while (reader.Read()) {
                if (reader.NodeType == XmlNodeType.Element) {
                    // reached a property/field element
                    if (currentPath.Contains(".")) {
                        int allowedDepth = reader.Depth - 1;

                        // if currentPath has more dots that allowedDepth, then remove the last part
                        string[] tmp = currentPath.Split('.');
                        if (tmp.Length - 1 > allowedDepth) {
                            currentPath = currentPath.Substring(0, currentPath.LastIndexOf('.'));
                        }
                    }

                    currentPath += "." + reader.Name;
                } else if (reader.NodeType == XmlNodeType.Text) {
                    // reached a value
                    string tmpValue = reader.ReadString();

                    if ((result = (tmpValue != null && tmpValue.Length > 0)) == false) {
                        throw new Exception(String.Format("Value for member '{0}' cannot be empty.", currentPath));
                    }

                    defaults.Add(currentPath, tmpValue);
                }
            }

            return result;
        }
    }
}
